/**
 * ***************************************************************************** Copyright (c) 2000,
 * 2012 IBM Corporation and others. All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0 which accompanies this
 * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html
 *
 * <p>Contributors: IBM Corporation - initial API and implementation Red Hat, Inc - changed
 * TarFileStructureProvider to TarLeveledStructureProvider
 * *****************************************************************************
 */
package org.eclipse.ui.wizards.datatransfer;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.zip.ZipEntry;
import org.eclipse.core.filesystem.URIUtil;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IPathVariableManager;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.osgi.util.NLS;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.actions.WorkspaceModifyOperation;
import org.eclipse.ui.dialogs.ContainerGenerator;
import org.eclipse.ui.dialogs.IOverwriteQuery;

/**
 * An operation which does the actual work of copying objects from the local file system into the
 * workspace.
 *
 * <p>This class may be instantiated; it is not intended to be subclassed.
 *
 * @noextend This class is not intended to be subclassed by clients.
 */
public class ImportOperation extends WorkspaceModifyOperation {
  private static final int POLICY_DEFAULT = 0;

  private static final int POLICY_SKIP_CHILDREN = 1;

  private static final int POLICY_FORCE_OVERWRITE = 2;

  private Object source;

  private IPath destinationPath;

  private IContainer destinationContainer;

  private List selectedFiles;

  private List rejectedFiles;

  private IImportStructureProvider provider;

  private IProgressMonitor monitor;

  protected IOverwriteQuery overwriteCallback;

  //    private Shell context;

  private List errorTable = new ArrayList();

  private boolean createVirtualFolder = false;

  private boolean createLinks = false;

  private boolean createLinkFilesOnly = false;

  private String relativeVariable = null;

  private boolean createContainerStructure = true;

  // The constants for the overwrite 3 state
  private static final int OVERWRITE_NOT_SET = 0;

  private static final int OVERWRITE_NONE = 1;

  private static final int OVERWRITE_ALL = 2;

  private int overwriteState = OVERWRITE_NOT_SET;

  private static final String ABSOLUTE_PATH = "<Absolute Path>"; // $NON-NLS-1$

  /**
   * Creates a new operation that recursively imports the entire contents of the specified root file
   * system object.
   *
   * <p>The <code>source</code> parameter represents the root file system object to import. All
   * contents of this object are imported. Valid types for this parameter are determined by the
   * supplied <code>IImportStructureProvider</code>.
   *
   * <p>The <code>provider</code> parameter allows this operation to deal with the source object in
   * an abstract way. This operation calls methods on the provider and the provider in turn calls
   * specific methods on the source object.
   *
   * <p>The default import behavior is to recreate the complete container structure for the contents
   * of the root file system object in their destination. If <code>setCreateContainerStructure
   * </code> is set to false then the container structure created is relative to the root file
   * system object.
   *
   * @param containerPath the full path of the destination container within the workspace
   * @param source the root file system object to import
   * @param provider the file system structure provider to use
   * @param overwriteImplementor the overwrite strategy to use
   */
  public ImportOperation(
      IPath containerPath,
      Object source,
      IImportStructureProvider provider,
      IOverwriteQuery overwriteImplementor) {
    super();
    this.destinationPath = containerPath;
    this.source = source;
    this.provider = provider;
    overwriteCallback = overwriteImplementor;
  }

  /**
   * Creates a new operation that imports specific file system objects. In this usage context, the
   * specified source file system object is used by the operation solely to determine the
   * destination container structure of the file system objects being imported.
   *
   * <p>The <code>source</code> parameter represents the root file system object to import. Valid
   * types for this parameter are determined by the supplied <code>IImportStructureProvider</code>.
   * The contents of the source which are to be imported are specified in the <code>filesToImport
   * </code> parameter.
   *
   * <p>The <code>provider</code> parameter allows this operation to deal with the source object in
   * an abstract way. This operation calls methods on the provider and the provider in turn calls
   * specific methods on the source object.
   *
   * <p>The <code>filesToImport</code> parameter specifies what contents of the root file system
   * object are to be imported.
   *
   * <p>The default import behavior is to recreate the complete container structure for the file
   * system objects in their destination. If <code>setCreateContainerStructure</code> is set to
   * <code>false</code>, then the container structure created for each of the file system objects is
   * relative to the supplied root file system object.
   *
   * @param containerPath the full path of the destination container within the workspace
   * @param source the root file system object to import from
   * @param provider the file system structure provider to use
   * @param overwriteImplementor the overwrite strategy to use
   * @param filesToImport the list of file system objects to be imported (element type: <code>Object
   *     </code>)
   */
  public ImportOperation(
      IPath containerPath,
      Object source,
      IImportStructureProvider provider,
      IOverwriteQuery overwriteImplementor,
      List filesToImport) {
    this(containerPath, source, provider, overwriteImplementor);
    setFilesToImport(filesToImport);
  }

  /**
   * Creates a new operation that imports specific file system objects.
   *
   * <p>The <code>provider</code> parameter allows this operation to deal with the source object in
   * an abstract way. This operation calls methods on the provider and the provider in turn calls
   * specific methods on the source object.
   *
   * <p>The <code>filesToImport</code> parameter specifies what file system objects are to be
   * imported.
   *
   * <p>The default import behavior is to recreate the complete container structure for the file
   * system objects in their destination. If <code>setCreateContainerStructure</code> is set to
   * <code>false</code>, then no container structure is created for each of the file system objects.
   *
   * @param containerPath the full path of the destination container within the workspace
   * @param provider the file system structure provider to use
   * @param overwriteImplementor the overwrite strategy to use
   * @param filesToImport the list of file system objects to be imported (element type: <code>Object
   *     </code>)
   */
  public ImportOperation(
      IPath containerPath,
      IImportStructureProvider provider,
      IOverwriteQuery overwriteImplementor,
      List filesToImport) {
    this(containerPath, null, provider, overwriteImplementor);
    setFilesToImport(filesToImport);
  }

  /**
   * Prompts if existing resources should be overwritten. Recursively collects existing read-only
   * files to overwrite and resources that should not be overwritten.
   *
   * @param sourceStart destination path to check for existing files
   * @param sources file system objects that may exist in the destination
   * @param noOverwrite files that were selected to be skipped (don't overwrite). object type IPath
   * @param overwriteReadonly the collected existing read-only files to overwrite. object type IPath
   * @param policy on of the POLICY constants defined in the class.
   */
  void collectExistingReadonlyFiles(
      IPath sourceStart,
      List sources,
      ArrayList noOverwrite,
      ArrayList overwriteReadonly,
      int policy) {
    IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();
    Iterator sourceIter = sources.iterator();
    IPath sourceRootPath = null;

    if (this.source != null) {
      sourceRootPath = new Path(provider.getFullPath(this.source));
    }
    while (sourceIter.hasNext()) {
      Object nextSource = sourceIter.next();
      IPath sourcePath = new Path(provider.getFullPath(nextSource));
      IPath newDestinationPath;
      IResource newDestination;

      if (sourceRootPath == null) {
        newDestinationPath = sourceStart.append(provider.getLabel(nextSource));
      } else {
        int prefixLength = sourcePath.matchingFirstSegments(sourceRootPath);
        IPath relativeSourcePath = sourcePath.removeFirstSegments(prefixLength);
        newDestinationPath = this.destinationPath.append(relativeSourcePath);
      }
      newDestination = workspaceRoot.findMember(newDestinationPath);
      if (newDestination == null) {
        continue;
      }

      IFolder folder = getFolder(newDestination);
      if (folder != null) {
        if (policy != POLICY_FORCE_OVERWRITE) {
          if (this.overwriteState == OVERWRITE_NONE || !queryOverwrite(newDestinationPath)) {
            noOverwrite.add(folder);
            continue;
          }
        }
        if (provider.isFolder(nextSource)) {
          collectExistingReadonlyFiles(
              newDestinationPath,
              provider.getChildren(nextSource),
              noOverwrite,
              overwriteReadonly,
              POLICY_FORCE_OVERWRITE);
        }
      } else {
        IFile file = getFile(newDestination);

        if (file != null) {
          if (!queryOverwriteFile(file, policy)) {
            noOverwrite.add(file.getFullPath());
          } else if (file.isReadOnly()) {
            overwriteReadonly.add(file);
          }
        }
      }
    }
  }

  /**
   * Creates the folders that appear in the specified resource path. These folders are created
   * relative to the destination container.
   *
   * @param path the relative path of the resource
   * @return the container resource coresponding to the given path
   * @exception CoreException if this method failed
   */
  IContainer createContainersFor(IPath path) throws CoreException {

    IContainer currentFolder = destinationContainer;

    int segmentCount = path.segmentCount();

    // No containers to create
    if (segmentCount == 0) {
      return currentFolder;
    }

    // Needs to be handles differently at the root
    if (currentFolder.getType() == IResource.ROOT) {
      return createFromRoot(path);
    }

    for (int i = 0; i < segmentCount; i++) {
      currentFolder = currentFolder.getFolder(new Path(path.segment(i)));
      if (!currentFolder.exists()) {
        if (createVirtualFolder) ((IFolder) currentFolder).create(IResource.VIRTUAL, true, null);
        else if (createLinks)
          ((IFolder) currentFolder).createLink(createRelativePath(path, currentFolder), 0, null);
        else ((IFolder) currentFolder).create(false, true, null);
      }
    }

    return currentFolder;
  }

  /**
   * Creates the folders that appear in the specified resource path assuming that the
   * destinationContainer begins at the root. Do not create projects.
   *
   * @param path the relative path of the resource
   * @return the container resource coresponding to the given path
   * @exception CoreException if this method failed
   */
  private IContainer createFromRoot(IPath path) throws CoreException {

    int segmentCount = path.segmentCount();

    // Assume the project exists
    IContainer currentFolder = ((IWorkspaceRoot) destinationContainer).getProject(path.segment(0));

    for (int i = 1; i < segmentCount; i++) {
      currentFolder = currentFolder.getFolder(new Path(path.segment(i)));
      if (!currentFolder.exists()) {
        ((IFolder) currentFolder).create(false, true, null);
      }
    }

    return currentFolder;
  }

  /**
   * Deletes the given resource. If the resource fails to be deleted, adds a status object to the
   * list to be returned by <code>getResult</code>.
   *
   * @param resource the resource
   */
  void deleteResource(IResource resource) {
    try {
      resource.delete(IResource.KEEP_HISTORY, null);
    } catch (CoreException e) {
      errorTable.add(e.getStatus());
    }
  }

  /* (non-Javadoc)
   * Method declared on WorkbenchModifyOperation.
   * Imports the specified file system objects from the file system.
   */
  protected void execute(IProgressMonitor progressMonitor) {

    monitor = progressMonitor;

    try {
      if (selectedFiles == null) {
        // Set the amount to 1000 as we have no idea of how long this will take
        monitor.beginTask("Importing:", 1000);
        ContainerGenerator generator = new ContainerGenerator(destinationPath);
        monitor.worked(30);
        validateFiles(Arrays.asList(new Object[] {source}));
        monitor.worked(50);
        destinationContainer = generator.generateContainer(new SubProgressMonitor(monitor, 50));
        importRecursivelyFrom(source, POLICY_DEFAULT);
        // Be sure it finishes
        monitor.worked(90);
      } else {
        // Choose twice the selected files size to take folders into account
        int creationCount = selectedFiles.size();
        monitor.beginTask("Importing:", creationCount + 100);
        ContainerGenerator generator = new ContainerGenerator(destinationPath);
        monitor.worked(30);
        validateFiles(selectedFiles);
        monitor.worked(50);
        destinationContainer = generator.generateContainer(new SubProgressMonitor(monitor, 50));
        importFileSystemObjects(selectedFiles);
        monitor.done();
      }
    } catch (CoreException e) {
      errorTable.add(e.getStatus());
    } finally {
      monitor.done();
    }
  }

  /**
   * Returns the container resource that the passed file system object should be imported into.
   *
   * @param fileSystemObject the file system object being imported
   * @return the container resource that the passed file system object should be imported into
   * @exception CoreException if this method failed
   */
  IContainer getDestinationContainerFor(Object fileSystemObject) throws CoreException {
    IPath pathname = new Path(provider.getFullPath(fileSystemObject));

    if (createContainerStructure) {
      return createContainersFor(pathname.removeLastSegments(1));
    }
    if (source == fileSystemObject) {
      return null;
    }
    IPath sourcePath = new Path(provider.getFullPath(source));
    IPath destContainerPath = pathname.removeLastSegments(1);
    IPath relativePath =
        destContainerPath.removeFirstSegments(sourcePath.segmentCount()).setDevice(null);
    return createContainersFor(relativePath);
  }

  /**
   * Returns the resource either casted to or adapted to an IFile.
   *
   * @param resource resource to cast/adapt
   * @return the resource either casted to or adapted to an IFile. <code>null</code> if the resource
   *     does not adapt to IFile
   */
  IFile getFile(IResource resource) {
    if (resource instanceof IFile) {
      return (IFile) resource;
    }
    Object adapted = ((IAdaptable) resource).getAdapter(IFile.class);
    if (adapted == null) {
      return null;
    }
    return (IFile) adapted;
  }

  /**
   * Returns the resource either casted to or adapted to an IFolder.
   *
   * @param resource resource to cast/adapt
   * @return the resource either casted to or adapted to an IFolder. <code>null</code> if the
   *     resource does not adapt to IFolder
   */
  IFolder getFolder(IResource resource) {
    if (resource instanceof IFolder) {
      return (IFolder) resource;
    }
    Object adapted = ((IAdaptable) resource).getAdapter(IFolder.class);
    if (adapted == null) {
      return null;
    }
    return (IFolder) adapted;
  }

  /**
   * Returns the rejected files based on the given multi status.
   *
   * @param multiStatus multi status to use to determine file rejection
   * @param files source files
   * @return list of rejected files as absolute paths. Object type IPath.
   */
  ArrayList getRejectedFiles(IStatus multiStatus, IFile[] files) {
    ArrayList filteredFiles = new ArrayList();

    IStatus[] status = multiStatus.getChildren();
    for (int i = 0; i < status.length; i++) {
      if (status[i].isOK() == false) {
        errorTable.add(status[i]);
        filteredFiles.add(files[i].getFullPath());
      }
    }
    return filteredFiles;
  }

  /**
   * Returns the status of the import operation. If there were any errors, the result is a status
   * object containing individual status objects for each error. If there were no errors, the result
   * is a status object with error code <code>OK</code>.
   *
   * @return the status
   */
  public IStatus getStatus() {
    IStatus[] errors = new IStatus[errorTable.size()];
    errorTable.toArray(errors);
    return new MultiStatus(
        PlatformUI.PLUGIN_ID,
        IStatus.OK,
        errors,
        "DataTransferMessages.ImportOperation_importProblems",
        null);
  }

  /**
   * Imports the specified file system object into the workspace. If the import fails, adds a status
   * object to the list to be returned by <code>getResult</code>.
   *
   * @param fileObject the file system object to be imported
   * @param policy determines how the file object is imported
   */
  void importFile(Object fileObject, int policy) {
    IContainer containerResource;
    try {
      containerResource = getDestinationContainerFor(fileObject);
    } catch (CoreException e) {
      IStatus coreStatus = e.getStatus();
      String newMessage =
          NLS.bind(
              "DataTransferMessages.ImportOperation_coreImportError",
              fileObject,
              coreStatus.getMessage());
      IStatus status =
          new Status(
              coreStatus.getSeverity(),
              coreStatus.getPlugin(),
              coreStatus.getCode(),
              newMessage,
              null);
      errorTable.add(status);
      return;
    }

    String fileObjectPath = provider.getFullPath(fileObject);
    monitor.subTask(fileObjectPath);
    IFile targetResource = containerResource.getFile(new Path(provider.getLabel(fileObject)));
    monitor.worked(1);

    if (rejectedFiles.contains(targetResource.getFullPath())) {
      return;
    }

    // ensure that the source and target are not the same
    IPath targetPath = targetResource.getLocation();
    // Use Files for comparison to avoid platform specific case issues
    if (targetPath != null && (targetPath.toFile().equals(new File(fileObjectPath)))) {
      errorTable.add(
          new Status(
              IStatus.ERROR,
              PlatformUI.PLUGIN_ID,
              0,
              NLS.bind(
                  "DataTransferMessages.ImportOperation_targetSameAsSourceError", fileObjectPath),
              null));
      return;
    }

    InputStream contentStream = provider.getContents(fileObject);
    if (contentStream == null) {
      errorTable.add(
          new Status(
              IStatus.ERROR,
              PlatformUI.PLUGIN_ID,
              0,
              NLS.bind("DataTransferMessages.ImportOperation_openStreamError", fileObjectPath),
              null));
      return;
    }

    try {
      if (createVirtualFolder || createLinks || createLinkFilesOnly) {
        if (targetResource.exists()) targetResource.delete(true, null);
        targetResource.createLink(
            createRelativePath(new Path(provider.getFullPath(fileObject)), targetResource),
            0,
            null);
      } else {
        if (targetResource.exists()) {
          if (targetResource.isLinked()) {
            targetResource.delete(true, null);
            targetResource.create(contentStream, false, null);
          } else targetResource.setContents(contentStream, IResource.KEEP_HISTORY, null);
        } else targetResource.create(contentStream, false, null);
      }
      setResourceAttributes(targetResource, fileObject);

      //            if (provider instanceof TarLeveledStructureProvider) {
      //            	try {
      //            		targetResource.setResourceAttributes(((TarLeveledStructureProvider)
      // provider).getResourceAttributes(fileObject));
      //            	} catch (CoreException e) {
      //            		errorTable.add(e.getStatus());
      //            	}
      //            }
    } catch (CoreException e) {
      errorTable.add(e.getStatus());
    } finally {
      try {
        contentStream.close();
      } catch (IOException e) {
        errorTable.add(
            new Status(
                IStatus.ERROR,
                PlatformUI.PLUGIN_ID,
                0,
                NLS.bind("DataTransferMessages.ImportOperation_closeStreamError", fileObjectPath),
                e));
      }
    }
  }

  /**
   * Reuse the file attributes set in the import.
   *
   * @param targetResource
   * @param fileObject
   */
  private void setResourceAttributes(IFile targetResource, Object fileObject) {

    long timeStamp = 0;
    if (fileObject instanceof File) {
      //			try {
      //				targetResource.setResourceAttributes(ResourceAttributes.fromFile((File)fileObject));
      timeStamp = ((File) fileObject).lastModified();
      //			} catch (CoreException e) {
      //        		errorTable.add(e.getStatus());
      //			}
    } /*else if (fileObject instanceof TarEntry) {
          try {
      		targetResource.setResourceAttributes(((TarLeveledStructureProvider) provider).getResourceAttributes(fileObject));
      		timeStamp = ((TarEntry)fileObject).getTime()*1000; // TarEntry time is in secs. Convert to msecs
      	} catch (CoreException e) {
      		errorTable.add(e.getStatus());
      	}
      }*/ else if (fileObject instanceof ZipEntry) {
      long zipTimeStamp = ((ZipEntry) fileObject).getTime();
      if (zipTimeStamp != -1) timeStamp = zipTimeStamp;
    }

    if (timeStamp != 0) {
      try {
        targetResource.setLocalTimeStamp(timeStamp);
      } catch (CoreException e) {
        errorTable.add(e.getStatus());
      }
    }
  }

  /**
   * Imports the specified file system objects into the workspace. If the import fails, adds a
   * status object to the list to be returned by <code>getStatus</code>.
   *
   * @param filesToImport the list of file system objects to import (element type: <code>Object
   *     </code>)
   * @throws CoreException
   * @exception OperationCanceledException if canceled
   */
  void importFileSystemObjects(List filesToImport) throws CoreException {
    Iterator filesEnum = filesToImport.iterator();
    while (filesEnum.hasNext()) {
      Object fileSystemObject = filesEnum.next();
      if (source == null) {
        // We just import what we are given into the destination
        IPath sourcePath = new Path(provider.getFullPath(fileSystemObject)).removeLastSegments(1);
        if (provider.isFolder(fileSystemObject) && sourcePath.isEmpty()) {
          // If we don't have a parent then we have selected the
          // file systems root. Roots can't copied (at least not
          // under windows).
          errorTable.add(
              new Status(
                  IStatus.INFO,
                  PlatformUI.PLUGIN_ID,
                  0,
                  "DataTransferMessages.ImportOperation_cannotCopy",
                  null));
          continue;
        }
        source = sourcePath.toFile();
      }
      importRecursivelyFrom(fileSystemObject, POLICY_DEFAULT);
    }
  }

  /**
   * Imports the specified file system container object into the workspace. If the import fails,
   * adds a status object to the list to be returned by <code>getResult</code>.
   *
   * @param folderObject the file system container object to be imported
   * @param policy determines how the folder object and children are imported
   * @return the policy to use to import the folder's children
   * @throws CoreException
   */
  int importFolder(Object folderObject, int policy) throws CoreException {
    IContainer containerResource;
    try {
      containerResource = getDestinationContainerFor(folderObject);
    } catch (CoreException e) {
      errorTable.add(e.getStatus());
      return policy;
    }

    if (containerResource == null) {
      return policy;
    }

    monitor.subTask(provider.getFullPath(folderObject));
    IWorkspace workspace = destinationContainer.getWorkspace();
    IPath containerPath = containerResource.getFullPath();
    IPath resourcePath = containerPath.append(provider.getLabel(folderObject));

    // Do not attempt the import if the resource path is unchanged. This may happen
    // when importing from a zip file.
    if (resourcePath.equals(containerPath)) {
      return policy;
    }

    if (workspace.getRoot().exists(resourcePath)) {
      if (rejectedFiles.contains(resourcePath)) {
        return POLICY_SKIP_CHILDREN;
      }

      IFolder folder = workspace.getRoot().getFolder(resourcePath);
      if (createVirtualFolder || createLinks || folder.isVirtual() || folder.isLinked()) {
        folder.delete(true, null);
      } else return POLICY_FORCE_OVERWRITE;
    }

    try {
      if (createVirtualFolder)
        workspace.getRoot().getFolder(resourcePath).create(IResource.VIRTUAL, true, null);
      else if (createLinks) {
        IFolder newFolder = workspace.getRoot().getFolder(resourcePath);
        newFolder.createLink(
            createRelativePath(new Path(provider.getFullPath(folderObject)), newFolder), 0, null);
        policy = POLICY_SKIP_CHILDREN;
      } else workspace.getRoot().getFolder(resourcePath).create(false, true, null);
    } catch (CoreException e) {
      errorTable.add(e.getStatus());
    }

    return policy;
  }

  /**
   * Transform an absolute path URI to a relative path one (i.e. from "C:\foo\bar\file.txt" to
   * "VAR\file.txt" granted that the relativeVariable is "VAR" and points to "C:\foo\bar\").
   *
   * @param location
   * @param resource
   * @return an URI that was made relative to a variable
   */
  private IPath createRelativePath(IPath location, IResource resource) {
    if (relativeVariable == null) return location;
    if (relativeVariable.equals(ABSOLUTE_PATH)) return location;
    IPathVariableManager pathVariableManager = resource.getPathVariableManager();
    try {
      return URIUtil.toPath(
          pathVariableManager.convertToRelative(URIUtil.toURI(location), true, relativeVariable));
    } catch (CoreException e) {
      return location;
    }
  }

  /**
   * Imports the specified file system object recursively into the workspace. If the import fails,
   * adds a status object to the list to be returned by <code>getStatus</code>.
   *
   * @param fileSystemObject the file system object to be imported
   * @param policy determines how the file system object and children are imported
   * @throws CoreException
   * @exception OperationCanceledException if canceled
   */
  void importRecursivelyFrom(Object fileSystemObject, int policy) throws CoreException {
    if (monitor.isCanceled()) {
      throw new OperationCanceledException();
    }

    if (!provider.isFolder(fileSystemObject)) {
      importFile(fileSystemObject, policy);
      return;
    }

    int childPolicy = importFolder(fileSystemObject, policy);
    if (childPolicy != POLICY_SKIP_CHILDREN) {
      Iterator children = provider.getChildren(fileSystemObject).iterator();
      while (children.hasNext()) {
        importRecursivelyFrom(children.next(), childPolicy);
      }
    }
  }

  /**
   * Queries the user whether the resource with the specified path should be overwritten by a file
   * system object that is being imported.
   *
   * @param resourcePath the workspace path of the resource that needs to be overwritten
   * @return <code>true</code> to overwrite, <code>false</code> to not overwrite
   * @exception OperationCanceledException if canceled
   */
  boolean queryOverwrite(IPath resourcePath) throws OperationCanceledException {
    String overwriteAnswer =
        overwriteCallback.queryOverwrite(resourcePath.makeRelative().toString());

    if (overwriteAnswer.equals(IOverwriteQuery.CANCEL)) {
      throw new OperationCanceledException("DataTransferMessages.DataTransfer_emptyString");
    }

    if (overwriteAnswer.equals(IOverwriteQuery.NO)) {
      return false;
    }

    if (overwriteAnswer.equals(IOverwriteQuery.NO_ALL)) {
      this.overwriteState = OVERWRITE_NONE;
      return false;
    }

    if (overwriteAnswer.equals(IOverwriteQuery.ALL)) {
      this.overwriteState = OVERWRITE_ALL;
    }

    return true;
  }

  /**
   * Returns whether the given file should be overwritten.
   *
   * @param targetFile the file to ask to overwrite
   * @param policy determines if the user is queried for overwrite
   * @return <code>true</code> if the file should be overwritten, and <code>false</code> if not.
   */
  boolean queryOverwriteFile(IFile targetFile, int policy) {
    // If force overwrite is on don't bother
    if (policy != POLICY_FORCE_OVERWRITE) {
      if (this.overwriteState == OVERWRITE_NOT_SET && !queryOverwrite(targetFile.getFullPath())) {
        return false;
      }
      if (this.overwriteState == OVERWRITE_NONE) {
        return false;
      }
    }
    return true;
  }

  //    /**
  //     * Sets the context for use by the VCM provider to prompt the user
  //     * for check-out of files.
  //     *
  //     * @param shell context for use by the VCM provider to prompt user
  //     * 	for check-out. The user will not be prompted if set to <code>null</code>.
  //     * @see IWorkspace#validateEdit(org.eclipse.core.resources.IFile[], Object)
  //     * @since 2.1
  //     */
  //    public void setContext(Shell shell) {
  //        context = shell;
  //    }

  /**
   * Sets whether the containment structures that are implied from the full paths of file system
   * objects being imported should be duplicated in the workbench.
   *
   * @param value <code>true</code> if containers should be created, and <code>false</code>
   *     otherwise
   */
  public void setCreateContainerStructure(boolean value) {
    createContainerStructure = value;
  }

  /**
   * Sets the file system objects to import.
   *
   * @param filesToImport the list of file system objects to be imported (element type: <code>Object
   *     </code>)
   */
  public void setFilesToImport(List filesToImport) {
    this.selectedFiles = filesToImport;
  }

  /**
   * Sets whether imported file system objects should automatically overwrite existing workbench
   * resources when a conflict occurs.
   *
   * @param value <code>true</code> to automatically overwrite, and <code>false</code> otherwise
   */
  public void setOverwriteResources(boolean value) {
    if (value) {
      this.overwriteState = OVERWRITE_ALL;
    }
  }

  /**
   * Validates that the given source resources can be copied to the destination as decided by the
   * VCM provider.
   *
   * @param existingFiles existing files to validate
   * @return list of rejected files as absolute paths. Object type IPath.
   */
  ArrayList validateEdit(List existingFiles) {

    if (existingFiles.size() > 0) {
      IFile[] files = (IFile[]) existingFiles.toArray(new IFile[existingFiles.size()]);
      IWorkspace workspace = ResourcesPlugin.getWorkspace();
      IStatus status = workspace.validateEdit(files, null);

      // If there was a mix return the bad ones
      if (status.isMultiStatus()) {
        return getRejectedFiles(status, files);
      }

      if (!status.isOK()) {
        // If just a single status reject them all
        errorTable.add(status);
        ArrayList filteredFiles = new ArrayList();

        for (int i = 0; i < files.length; i++) {
          filteredFiles.add(files[i].getFullPath());
        }
        return filteredFiles;
      }
    }
    return new ArrayList();
  }

  /**
   * Validates the given file system objects. The user is prompted to overwrite existing files.
   * Existing read-only files are validated with the VCM provider.
   *
   * @param sourceFiles files to validate
   */
  void validateFiles(List sourceFiles) {
    ArrayList noOverwrite = new ArrayList();
    ArrayList overwriteReadonly = new ArrayList();

    collectExistingReadonlyFiles(
        destinationPath, sourceFiles, noOverwrite, overwriteReadonly, POLICY_DEFAULT);
    rejectedFiles = validateEdit(overwriteReadonly);
    rejectedFiles.addAll(noOverwrite);
  }

  /**
   * Set Whether groups and links will be created instead of files and folders
   *
   * @param virtualFolders
   * @since 3.6
   */
  public void setVirtualFolders(boolean virtualFolders) {
    createVirtualFolder = virtualFolders;
  }

  /**
   * Set Whether links will be created instead of files and folders
   *
   * @param links
   * @since 3.6
   */
  public void setCreateLinks(boolean links) {
    createLinks = links;
  }

  /**
   * Set a variable relative to which the links are created
   *
   * @param variable
   * @since 3.6
   */
  public void setRelativeVariable(String variable) {
    relativeVariable = variable;
  }
}
